using Microsoft.Synchronization;
using Microsoft.Synchronization.Files;
using Sandia.MVCFramework.Common;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace Sandia.MVCFramework.Plugin
{
    /// <summary>
    /// A delegate for events related to progress changes during an update.
    /// </summary>
    public delegate void OnUpdateProgressChanged(object sender, int percentComplete);

    /// <summary>
    /// A plugin updater that updates plugins from a source directory into a
    /// plugin directory.
    /// </summary>
    public class StandardPluginUpdater : IPluginUpdater
    {
        /// <summary>
        /// The directory to get the plugins from.
        /// </summary>
        private DirectoryInfo sourceDirectory;

        /// <summary>
        /// The directory to put the plugins.
        /// </summary>
        private DirectoryInfo pluginDirectory;

        /// <summary>
        /// The current total of applied changes for a sync.
        /// </summary>
        private int appliedChanges = 0;

        /// <summary>
        /// The total number of changes found for a sync.
        /// </summary>
        private int numChanges = 0;

        /// <summary>
        /// An event called when an update is applying a change.
        /// </summary>
        public event EventHandler<ApplyingChangeEventArgs> ApplyingChange;

        /// <summary>
        /// An event called when an update is copying a file.
        /// </summary>
        public event EventHandler<CopyingFileEventArgs> CopyingFile;

        /// <summary>
        /// An event called when a change is applied during an update.
        /// </summary>
        public event EventHandler<AppliedChangeEventArgs> AppliedChange;

        /// <summary>
        /// An event called when the overall progress of an update is changed.
        /// </summary>
        public event OnUpdateProgressChanged UpdateProgessChanged;

        /// <summary>
        /// Constructor for StandardPluginUpdater.
        /// </summary>
        /// <param name="sourceDirectory">
        /// The directory to get the plugins from.
        /// </param>
        /// <param name="pluginDirectory">
        /// The directory to put the plugins.
        /// </param>
        public StandardPluginUpdater(
            string sourceDirectory,
            string pluginDirectory)
        {
            this.sourceDirectory = new DirectoryInfo(sourceDirectory);
            this.pluginDirectory = new DirectoryInfo(pluginDirectory);

            // Check input parameters.
            if (!this.sourceDirectory.Exists)
            {
                LoggingTools.SendToLog(
                    "StandardPluginUpdater: sourceDirectory does not exist.",
                    LoggingCategories.ERROR,
                    100);
                return;
            }

            // If the destination directory doesn't exist, create it.
            if (!this.pluginDirectory.Exists)
            {
                this.pluginDirectory.Create();
            }
        }

        /// <summary>
        /// Called when a file is being copied during a sync. Passes the event on.
        /// </summary>
        /// <param name="sender">FileSyncProvider</param>
        /// <param name="e">The event arguments.</param>
        void destinationReplica_CopyingFile(object sender, CopyingFileEventArgs e)
        {
            if (this.CopyingFile != null)
            {
                this.CopyingFile(this, e);
            }
        }

        /// <summary>
        /// Called when a change has been applied during a sync. Passes the event on
        /// and also calculates total progress and fires the event.
        /// </summary>
        /// <param name="sender">FileSyncProvider</param>
        /// <param name="e">The event arguments.</param>
        void destinationReplica_AppliedChange(object sender, AppliedChangeEventArgs e)
        {
            if (this.AppliedChange != null)
            {
                this.AppliedChange(this, e);
            }
            if (this.UpdateProgessChanged != null)
            {
                this.appliedChanges++;
                int progress = (int)((((double)this.appliedChanges / (double)this.numChanges)) * 100.0);
                this.UpdateProgessChanged(this, progress);
            }
        }

        /// <summary>
        /// Called when a change is being applied during a sync. Passes the event on.
        /// </summary>
        /// <param name="sender">FileSyncProvider</param>
        /// <param name="e">The event arguments.</param>
        void destinationReplica_ApplyingChange(object sender, ApplyingChangeEventArgs e)
        {
            if (this.ApplyingChange != null)
            {
                this.ApplyingChange(this, e);
            }
        }

        /// <summary>
        /// Called when a change is applied in preview mode. This is used to gather
        /// information for progress reporting.
        /// </summary>
        /// <param name="sender">The FileSyncProvider.</param>
        /// <param name="e">The event arguments.</param>
        void applyingChange(
            object sender, 
            ApplyingChangeEventArgs e)
        {
            this.numChanges++;
        }

        /// <summary>
        /// A method used to get or create the unique sync Guid. This Guid is
        /// stored in a file called Synchronization.ID in the target directory.
        /// </summary>
        /// <param name="syncDirInfo">The directory to get the Guid for.</param>
        /// <param name="idDirInfo">The directory where the Synchronization.ID file should be located.</param>
        /// <returns>The Guid.</returns>
        private Guid getSyncID(
            DirectoryInfo syncDirInfo,
            DirectoryInfo idDirInfo)
        {
            // We put a file in each sync directory called "Synchronization.ID"
            // that contains the ID to be used for synchronization.
            FileInfo syncIDFileInfo = 
                new FileInfo(
                    Path.Combine(idDirInfo.FullName, "Synchronization.ID"));
            // If an ID has already been created for this directory.
            if (!syncIDFileInfo.Exists)
            {
                File.Create(syncIDFileInfo.FullName).Close();
            }
            // Read the Guid from the file and return.
            StreamReader reader = new StreamReader(syncIDFileInfo.FullName);
            string id = null;
            while (!reader.EndOfStream)
            {
                string[] idInfo = reader.ReadLine().Split(new char[] { ',' });
                if (idInfo[0] == syncDirInfo.FullName)
                {
                    id = idInfo[1];
                }
            }
            reader.Close();
            if (id != null)
            {
                return new Guid(id);
            }
            //Otherwise, we need to create a new Guid and file for this directory.
            Guid guid = Guid.NewGuid();
            StreamWriter writer =
                new StreamWriter(syncIDFileInfo.FullName, true);
            writer.WriteLine(syncDirInfo.FullName + "," + guid.ToString());
            writer.Close();
            return guid;
        }

        #region IPluginUpdater Members

        /// <summary>
        /// Indicates whether an update is required.
        /// </summary>
        public bool UpdateRequired
        {
            get
            {
                // Reset the number of changes found for progress reporting.
                this.numChanges = 0;

                Guid sourceSyncID = this.getSyncID(this.sourceDirectory, this.pluginDirectory);
                Guid pluginSyncID = this.getSyncID(this.pluginDirectory, this.pluginDirectory);

                // Inititialize the file sync providers.
                FileSyncProvider sourceReplica =
                    new FileSyncProvider(
                        sourceSyncID,
                        this.sourceDirectory.FullName, 
                        null, 
                        FileSyncOptions.None,
                        this.pluginDirectory.FullName, 
                        "filesync" + sourceSyncID.ToString() + ".metadata",
                        this.pluginDirectory.FullName, 
                        null);
                // We use preview mode here, this allows us to go through the
                // synch process without making any changes. This makes it
                // possible to collect information for progress reporting later.
                sourceReplica.PreviewMode = true;

                FileSyncProvider destinationReplica =
                    new FileSyncProvider(
                        pluginSyncID,
                        this.pluginDirectory.FullName,
                        null, 
                        FileSyncOptions.None,
                        this.pluginDirectory.FullName,
                        "filesync" + pluginSyncID.ToString() + ".metadata",
                        this.pluginDirectory.FullName,
                        null);
                // We use preview mode here, this allows us to go through the
                // synch process without making any changes. This makes it
                // possible to collect information for progress reporting later.
                destinationReplica.PreviewMode = true;
                // This event counts the changes.
                destinationReplica.ApplyingChange +=
                    new EventHandler<ApplyingChangeEventArgs>(this.applyingChange);

                SyncOrchestrator syncOrchestrator = new SyncOrchestrator();

                // Assign the plug-in source directory as the "remote" provider, and
                // the plug-in directory as the "local" provider. These settings are
                // combined with the "Direction" field to indicate how the sync
                // should happen.
                syncOrchestrator.RemoteProvider = sourceReplica;
                syncOrchestrator.LocalProvider = destinationReplica;
                // We want to download from the "remote" provider to the "local"
                // provider.
                syncOrchestrator.Direction = SyncDirectionOrder.Download;
                // Perform the sync.
                syncOrchestrator.Synchronize();

                // Update is only required if there are changes.
                return this.numChanges > 0;
            }
        }

        /// <summary>
        /// Update the plugins.
        /// </summary>
        public void Update()
        {
            // Reset the applied changes for status tracking.
            this.appliedChanges = 0;

            Guid sourceSyncID = this.getSyncID(this.sourceDirectory, this.pluginDirectory);
            Guid pluginSyncID = this.getSyncID(this.pluginDirectory, this.pluginDirectory);

            // Inititialize the file sync providers.
            FileSyncProvider sourceReplica =
                new FileSyncProvider(
                    sourceSyncID,
                    this.sourceDirectory.FullName,
                    null,
                    FileSyncOptions.None,
                    this.pluginDirectory.FullName,
                    "filesync" + sourceSyncID.ToString() + ".metadata",
                    this.pluginDirectory.FullName,
                    null);

            FileSyncProvider destinationReplica =
                new FileSyncProvider(
                    pluginSyncID,
                    this.pluginDirectory.FullName,
                    null,
                    FileSyncOptions.None,
                    this.pluginDirectory.FullName,
                    "filesync" + pluginSyncID.ToString() + ".metadata",
                    this.pluginDirectory.FullName,
                    null);
            destinationReplica.ApplyingChange +=
                new EventHandler<ApplyingChangeEventArgs>(destinationReplica_ApplyingChange);
            destinationReplica.CopyingFile +=
                new EventHandler<CopyingFileEventArgs>(destinationReplica_CopyingFile);
            destinationReplica.AppliedChange +=
                new EventHandler<AppliedChangeEventArgs>(destinationReplica_AppliedChange);

            SyncOrchestrator syncOrchestrator = new SyncOrchestrator();

            // Assign the plug-in source directory as the "remote" provider, and
            // the plug-in directory as the "local" provider. These settings are
            // combined with the "Direction" field to indicate how the sync
            // should happen.
            syncOrchestrator.RemoteProvider = sourceReplica;
            syncOrchestrator.LocalProvider = destinationReplica;
            // We want to download from the "remote" provider to the "local"
            // provider.
            syncOrchestrator.Direction = SyncDirectionOrder.Download;
            // Perform the sync.
            syncOrchestrator.Synchronize();
        }

        #endregion
    }
}
